﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Xml;

using BlueCMS.Core.Configuration;
using BlueCMS.Core.IoC.Configuration;

// TODO: Handle IDisposable
namespace BlueCMS.Core.IoC
{
    public static class Container
    {
        private static readonly Dictionary<Type, Component> components = new Dictionary<Type, Component>();

        private static bool isLoaded = false;

        public static T Resolve<T>() where T : class
        {
            return (T)Resolve(typeof(T), "default");
        }

        public static T Resolve<T>(string name) where T : class
        {
            return (T)Resolve(typeof(T), name);
        }

        public static object Resolve(Type type)
        {
            return Resolve(type, "default");
        }

        public static object Resolve(Type type, string name)
        {
            if (!isLoaded)
            {
                LoadConfiguration();
            }

            if (components.ContainsKey(type))
            {
                return GetImplementationFromComponent(type, components[type], name);
            }

            throw new ContainerException(String.Format("No implementation found for the requested contract: {0}", type.Name));
        }

        private static T GetImplementationFromComponent<T>(Component c, string name) where T : class
        {
            return (T)GetImplementationFromComponent(typeof(T), c, name);
        }

        private static object GetImplementationFromComponent(Type t, Component c, string name)
        {
            if (!c.Implementations.ContainsKey(name))
                return null;

            Implementation i = c.Implementations[name];

            switch (c.Scope)
            {
                case ComponentScope.Singleton:
                    {
                        return i.Instance;
                    }
                    break;
                case ComponentScope.HttpContext:
                    {
                        // TODO: Come up with a better key creation mechanism
                        string key = String.Format("BlueCMS.IoC:{0}", i.Type.FullName);
                        object ret = HttpContext.Current.Items[key];
                        if (ret == null)
                        {
                            ret = CreateInstanceFromImplementation(i);
                            HttpContext.Current.Items[key] = ret;
                        }
                        return ret;
                    }
                    break;
            }

            return CreateInstanceFromImplementation(i);
        }

        private static object CreateInstanceFromImplementation(Implementation i)
        {
            return CreateInstanceFromImplementation<object>(i);
        }

        private static T CreateInstanceFromImplementation<T>(Implementation i)
        {
            if (i.Properties.Count == 0)
            {
                return (T)Activator.CreateInstance(i.Type);
            }

            // Find a constructor that matches our properties
            List<ConstructorInfo> constructors = new List<ConstructorInfo>(i.Type.GetConstructors());
            constructors.OrderByDescending(ci => ci.GetParameters().Length);
            
            foreach (ConstructorInfo ci in constructors)
            {
                List<object> p = new List<object>();
                ParameterInfo[] parameters = ci.GetParameters();
                foreach (ParameterInfo pi in parameters)
                {
                    if (i.Properties.ContainsKey(pi.Name))
                    {
                        TypeConverter tc = TypeDescriptor.GetConverter(pi.ParameterType);
                        p.Add(tc.ConvertFromString(i.Properties[pi.Name]));
                    }
                    else if(CanResolve(pi.ParameterType))
                    {
                        p.Add(Resolve(pi.ParameterType));
                    }
                }

                if (p.Count == parameters.Length)
                {
                    return (T)ci.Invoke(p.ToArray());
                }
            }

            throw new ContainerException(String.Format("No constructor found for implementation: {0}", i.Type.Name));
        }

        private static bool CanResolve(Type type)
        {
            if (!isLoaded)
            {
                LoadConfiguration();
            }

            if (components.ContainsKey(type))
            {
                return true;
            }

            return false;
        }

        private static void LoadConfiguration()
        {
            if (isLoaded)
                return;

            lock (components)
            {
                if (isLoaded)
                    return;

                ContainerConfiguration containerConfig = ((CmsConfiguration)System.Configuration.ConfigurationManager.GetSection("BlueCMS")).ContainerConfiguration;

                if (containerConfig != null)
                {

                    foreach (ComponentConfiguration componentConfig in containerConfig.Components)
                    {
                        Component component = new Component();
                        component.Scope = componentConfig.Scope;

                        Type contract = Type.GetType(componentConfig.Contract);

                        foreach (InstanceConfiguration instanceConfig in componentConfig.Instances)
                        {
                            Implementation implementation = new Implementation();
                            implementation.Type = Type.GetType(instanceConfig.Implementation);

                            foreach (ParameterConfiguration propertyConfig in instanceConfig.Parameters)
                            {
                                implementation.Properties.Add(propertyConfig.Name, propertyConfig.Value);
                            }

                            if (component.Scope == ComponentScope.Singleton)
                            {
                                implementation.Instance = CreateInstanceFromImplementation(implementation);
                            }

                            component.Implementations.Add(instanceConfig.Name, implementation);
                        }

                        components.Add(contract, component);
                    }
                }

                isLoaded = true;
            }
        }
    }
}
